AWS Amplify + ReactでS3オブジェクトのカスタムメタデータを読み書きする
AWS Amplify + React で S3オブジェクトのカスタムデータの読み書きをしてみました。
準備
React Appを作成する
アプリ名をmy-s3-controller
としました。
$ npx create-react-app my-s3-controller $ cd my-s3-controller
Amplifyをセットアップする
$ amplify init ? Enter a name for the environment dev ? Choose your default editor: Vim (via Terminal, macOS only) ? Choose the type of app that you're building javascript Please tell us about your project ? What javascript framework are you using react ? Source Directory Path: src ? Distribution Directory Path: build ? Build Command: npm run-script build ? Start Command: npm run-script start Using default provider awscloudformation ? Select the authentication method you want to use: AWS profile ? Please choose the profile you want to use [select your profile] $ npm install aws-amplify @aws-amplify/ui-react
Amplify Authモジュールを追加する
Amplify Authモジュールを追加します。 ここではユーザーはEmailでログインする設定としました。
$ amplify auth add Do you want to use the default authentication and security configuration? Default configuration How do you want users to be able to sign in? Email Do you want to configure advanced settings? No, I am done. $ amplify push
Cognito ユーザープールが作成されます。
AWSコンソールで、作成されたユーザープールのプール ID
と(*_app_clientWebのID
)をメモし、IDプールのCognitoの接続設定に入力して、Cognito IDプールを作成します。
Cognito IDプール作成時にロールも作成することになりますが(アプリ名がmy-s3-controllerなのでデフォルトではロール名はCognito_MyS3ControllerAuth_Role
になります)、このロールにs3:ListBucket
、s3:GetObject
、s3:PutObject
を与えておきまます。
既存のS3バケットに接続する
今回は既存のS3バケットを使いたかったので、src/index.js
に下記の修正を行います。
import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; + import Amplify from 'aws-amplify'; + import awsExports from './aws-exports'; + Amplify.configure(awsExports); + + Amplify.configure({ + Auth: { + identityPoolId: process.env.REACT_APP_COGNITO_IDENTITY_POOL_ID, + region: process.env.REACT_APP_COGNITO_REGION + }, + Storage: { + AWSS3: { + bucket: process.env.REACT_APP_S3_BUCKET, + region: process.env.REACT_APP_S3_REGION + } + } + }); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
.env
ファイルを下記のように作ります。
REACT_APP_COGNITO_IDENTITY_POOL_ID=「接続するCognito IDプールのプールID REACT_APP_COGNITO_REGION=[接続するCognitoIDプールのリージョン] REACT_APP_S3_BUCKET=[接続するS3バケット名] REACT_APP_S3_REGION=[接続するS3バケットのリージョン]
ここで、一旦、npm start
してhttp://localhost:3000
でReactのデフォルト画面が表示されることを確認しておきます。
$ npm start
接続するS3バケットのCORS設定を行う
http://localhost:3000
で稼働するアプリからS3バケットにオブジェクト(ファイル)を作るためにはCORS(Cross-Origin Resource Sharing)
の設定が必要になります。
下記のようにS3バケットのCORSを設定します。
[ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "HEAD", "GET", "PUT", "POST", "DELETE" ], "AllowedOrigins": [ "http://localhost:3000" ], "ExposeHeaders": [ "x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2", "ETag", "x-amz-meta-foo" ], "MaxAgeSeconds": 3000 } ]
AllowedOrigins
にCORSを許可するアプリのURL(http://localhost:3000)を指定します。
また、カスタムメタデータfoo
を読み書きしたいので、ExposeHeaders
にx-amz-meta-foo
を追加しています。この設定を行なっておかないと、当該カスタムメタデータの読み込みができません(書き込みは設定していなくてもできます)。nameというカスタムメタデータを読み込みたい場合はx-amz-meta-nameを追加する必要があります。
認証ユーザを作る
src/App.js
を下記に修正してCognitoの認証を通してアプリを使うように変更します。
import logo from './logo.svg'; import './App.css'; + import React from 'react' + import { Authenticator } from '@aws-amplify/ui-react'; + import '@aws-amplify/ui-react/styles.css'; - function App() { + const App = () => { return ( - <div className="App"> - <header className="App-header"> - <img src={logo} className="App-logo" alt="logo" /> - <p> - Edit <code>src/App.js</code> and save to reload. - </p> - <a - className="App-link" - href="https://reactjs.org" - target="_blank" - rel="noopener noreferrer" - > - Learn React - </a> - </header> - </div> - ); + <Authenticator> + {({ signOut, user }) => ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Edit <code>src/App.js</code> and save to reload. + </p> + <a + className="App-link" + href="https://reactjs.org" + target="_blank" + rel="noopener noreferrer" + > + Learn React + </a> + </header> + </div> + )} + </Authenticator> + ) } - export default App; + export default App
npm start
して、アプリにアクセスするとCognitoの認証画面が表示されます。
アプリを利用するために、Create Accountタブからユーザーを作成します。
書き込み
AmplifyのStorage.put
メソッドを使うことでS3にオブジェクトを作成できます。
この際に、カスタムメタデータはmetadata
キーで指定します。カスタムメタデータfoo
の値に日本語を使えるようにencodeURIComponent
でエンコードします。
const uploadFile = async(file, foo) => { if (!foo) return if (!file) return const fileName = Date.now() + '.' + file.type.replace(/(.*)\//g, '') const result = await Storage.put(fileName, file, { level: 'protected', contentType: file.type, metadata: { foo: encodeURIComponent(foo) } }) if (result) { console.log(result) } else { console.error(result.error) }
uploadFile
はS3にアップロードするファイルのfileオブジェクトとfooの値を受け取ってStorage.put
を呼び出しています。 また、level
にprotected
を指定して他のユーザがアップしたファイルも読み取れる指定をしています(ただし、identityId
も指定しないと、protected
の場合は他のユーザがアップしたファイルは読み取れません)。
Storage - Upload files - JavaScript - AWS Amplify Docs
読み込み
S3にアップロードしたオブジェクトの読み込みにはAmplifyではStorage.get
メソッドやStorage.list
メソッド提供されています。しかし、これらのメソッドでカスタムメタデータを読み出すことは2022/05/01現在できません。したがって、カスタムメタデータを読み出したい場合は、S3 Client - AWS SDK for JavaScript v3を使う必要があります。
AWS SDKをインストールします。
$ npm install aws-sdk
カスタムメタデータの読み出しは次のように行います。
import React from 'react' import { Auth } from 'aws-amplify' import { S3Client, HeadObjectCommand, ListObjectsV2Command } from '@aws-sdk/client-s3' const fetchS3Objects = async (bucket) => { try { const s3client = new S3Client({ region: process.env.REACT_APP_S3_REGION, credentials: await Auth.currentCredentials() }) const output = await s3client.send( new ListObjectsV2Command({ Bucket: bucket }) ) if (!output.Contents) return [] const heads = [] for (let i =0; i < output.Contents.length; i++) { const c = output.Contents[i] const head = await s3client.send( new HeadObjectCommand({ Bucket: bucket, Key: c.Key }) ) heads.push({ foo: decodeURIComponent(head.Metadata.foo) }) } return heads } catch (err) { console.error(err) } }
ListObjectsV2Command
メソッドでバケット内にあるオブジェクトの一覧情報を得ます。ここには各オブジェクトのKeyが含まれているので、さらに各Keyに対して、HeadObjectCommand
メソッドを呼び出しカスタムメタデータをhead.Metadata.foo
で取得しています。
ここで気を付ける必要があるのは、ListObjectsV2Command
は{ level: 'protected' }
で制限をかけたオブジェクトだけではなく、バケット内の全オブジェクトを取得するということです。ですので、出力を{ level: 'protected' }
のオブジェクトに限定したい場合は、別途Storage.list
メソットなどでprotectedに限定された一覧を取得して、ListObjectsV2Command
で得たカスタムメタデータをマージする必要があります。
まとめ
AWS Amplify + ReactでS3オブジェクトのカスタムメタデータを読み書きする方法に関して記述しました。
カスタムメタデータの書き込みはシンプルなのですが、読み込みはCORSのヘッダの設定やaws-sdkの利用など実現には一工夫が必要でした。
参考になれば幸いです。